﻿using G19Glower.Shared;
using System;
using System.Drawing;
using System.Windows.Forms;

namespace G19Glower.Glowies
{
    public class LinearGlower
    {
        private IGlowController mController;
        private IKeyboardListener mKeyboardListener;

        private Color mBaseColour = Color.FromArgb(28,0,0); // dark red
        private Color mTargetColour = Color.Red;

        private double mIntensity = 0;
        private double mEffectiveIntensity = 0;
        private double mIncrementValue = 0.15;
        private int mUpdateSpeed = 35;
        private DateTime mLastKeyPress = DateTime.Now;
        private TimeSpan mFadeoutTime = TimeSpan.FromMilliseconds(1750);
        private TimeSpan mFreezeTime = TimeSpan.FromMilliseconds(500);

        private bool mRunning = false;
        private System.Windows.Forms.Timer mTimer;

        #region .ctor

        public LinearGlower(IGlowController controller, IKeyboardListener keyboardListener)
        {
            mTimer = new System.Windows.Forms.Timer();
            mTimer.Interval = mUpdateSpeed;
            mTimer.Tick += new EventHandler(Timer_Tick);

            mController = controller;
            mKeyboardListener = keyboardListener;
        }

        #endregion

        #region Public Properties

        public Color IdleColour
        {
            get { return mBaseColour; }
            set
            {
                mBaseColour = value;
                this.Update();
            }
        }

        public Color GlowColour
        {
            get { return mTargetColour; }
            set
            {
                mTargetColour = value;
                Update();
            }
        }

        public Double IncrementValue
        {
            get { return mIncrementValue; }
            set { mIncrementValue = value; }
        }

        public TimeSpan FadeoutTime
        {
            get { return mFadeoutTime; }
            set { mFadeoutTime = value; }
        }

        public TimeSpan FreezeTime
        {
            get { return mFreezeTime; }
            set { mFreezeTime = value; }
        }

        public bool IsRunning { get { return mRunning; } }

        #endregion

        #region Public Methods
        
        public void Start()
        {
            if (mRunning)
            {
                throw new InvalidOperationException();
            }

            mKeyboardListener.KeyPressed += this.KeyboardHooker_KeyPressed;
            mController.SetColor(mBaseColour.R, mBaseColour.G, mBaseColour.B);

            mRunning = true;
        }

        public void Stop()
        {
            if (!mRunning)
            {
                throw new InvalidOperationException();
            }

            mRunning = false;
            mKeyboardListener.KeyPressed -= this.KeyboardHooker_KeyPressed;
        }

        #endregion

        #region Event Handlers

        private void Timer_Tick(object sender, EventArgs e)
        {
            this.Update();
        }

        private void KeyboardHooker_KeyPressed(Keys obj)
        {
            mLastKeyPress = DateTime.Now;
            mIntensity = Math.Min(1.0, mEffectiveIntensity + mIncrementValue);

            if (!this.mTimer.Enabled)
            {
                this.Update();
                mTimer.Start();
            }
        }

        #endregion

        #region Private Methods

        private void Update()
        {
            // Work out current time state
            double timePassed = (DateTime.Now - mLastKeyPress).TotalMilliseconds;

            double tweenValue = 0;
            if (timePassed < mFreezeTime.TotalMilliseconds)
            {
                tweenValue = mIntensity;
            }
            else
            {
                timePassed -= mFreezeTime.TotalMilliseconds;
                double effectiveFalloutTime = mFadeoutTime.TotalMilliseconds * mIntensity;

                if (timePassed < effectiveFalloutTime)
                {
                    tweenValue = mIntensity - (mIntensity * (timePassed / effectiveFalloutTime));
                }
            }

            // Tween from base to glow by state
            byte r = Tween(mBaseColour.R, mTargetColour.R, tweenValue);
            byte g = Tween(mBaseColour.G, mTargetColour.G, tweenValue);
            byte b = Tween(mBaseColour.B, mTargetColour.B, tweenValue);

            // Update keyboard
            mController.SetColor(r, g, b);

            // Update state
            if (tweenValue == 0)
            {
                mIntensity = 0;
                mEffectiveIntensity = 0;
                mTimer.Stop();
            }
            else
            {
                mEffectiveIntensity = tweenValue;
            }
        }

        #endregion

        #region Helper Methods

        private static byte Tween(byte sourceByte, byte targetByte, double percent)
        {
            double source = (int)sourceByte;
            double target = (int)targetByte;
            return (byte)(source + ((target - source) * percent));
        }

        #endregion
    }
}